home *** CD-ROM | disk | FTP | other *** search
/ Apple Developer Connection 1998 Fall: Game Toolkit / Disc.iso / SDKs / PCI Driver Development Kit / • Tools / Utility / DisplayNameRegistry 950412 / Src / TwistDownList.c < prev    next >
Encoding:
C/C++ Source or Header  |  1997-02-27  |  66.2 KB  |  2,194 lines  |  [TEXT/MPCC]

  1. /*                                    TwistDownList.c                                */
  2. /*
  3.  * List In A List Sample
  4.  * TwistDownList.c
  5.  * Copyright © 1993-94 Apple Computer Inc. All rights reserved.
  6.  *
  7.  * TwistDownList manages all aspects of the "twist-down" list, a one-column text
  8.  * list where each row contains a triangular button. The button may be in one of
  9.  * two states: closed and opened. When opened, sub-elements to this row are
  10.  * displayed. The twist-down list is generally based on code from NewsWatcher
  11.  * by Steve Falkenburg, but Steve says that the code was originally written by
  12.  * John Norstad.
  13.  *
  14.  * Applications must provide a LDEF stub resource by adding the following (or
  15.  * something equivalent) to the resource definition file:
  16.  *        #ifndef LDEF_Stub
  17.  *        #define LDEF_Stub    1024
  18.  *        #endif
  19.  *        type 'LDEF' {
  20.  *        unsigned hex longint = $41FA0006;    //    lea        pc+8,a0    ; a0 -> ProcPtr
  21.  *        unsigned hex int = $2050;            //    movea.l    (a0),a0    ; a0 -> LDEF
  22.  *        unsigned hex int = $4ED0;            //    jmp        (a0)    ; jump to it
  23.  *        unsigned hex longint = 0;            //    dc.l    0        ; LDEF ProcPtr
  24.  *        };
  25.  *        resource 'LDEF' (LDEF_Stub, "Stub LDEF", preload, locked) { };
  26.  *
  27.  * Because of the way that the callback LDEF is managed, TwistDownList.c must be
  28.  * in the application's root segment. If this is inappropriate, the segement
  29.  * resource must be marked "preload" and "locked" -- otherwise, your program will
  30.  * crash. (Note that this applies only to 68000 code.)
  31.  */
  32. #include <stdio.h>
  33. #include "TwistDownList.h"
  34. #include "Palettes.h"
  35. #include "IntlResources.h"
  36. #include "Script.h"
  37. #include "TextUtils.h"
  38. #include "SegLoad.h"
  39. #include "FixMath.h"
  40. #include "ToolUtils.h"
  41. #include "Gestalt.h"
  42. #include "Resources.h"
  43. #include "Packages.h"
  44. #ifdef __powerc
  45. /*
  46.  * PowerPC System area locations in "Get" and "Set" functions. They are
  47.  * defined in LowMem.h
  48.  */
  49. #include <LowMem.h>
  50. #else
  51. #ifdef MPW
  52. /*
  53.  * MPW requires a reference to a low memory global to set hiliting
  54.  * Think C and MetroWorks includes SysEqu.h in the default MacHeaders file.
  55.  */
  56. #include <SysEqu.h>
  57. #endif
  58. #endif
  59.  
  60. #define UNUSED(what) do {    \
  61.             what;            \
  62.         } while (0)
  63.  
  64. #ifndef MONOCHROME_FILL
  65. #define MONOCHROME_FILL    0
  66. #endif
  67.  
  68. #define LIST    (**theList)
  69. /*
  70.  * A common List Selection flag variation that equates Shift and Command keys.
  71.  */
  72. #define SELECTION_FLAGS    \
  73.     (lUseSense | lNoRect | lNoExtend | lNoNilHilite | lDoVAutoscroll)
  74. /*
  75.  * SELECTION_FLAGS, by default, allows a single list cell to be selected.
  76.  */ 
  77. #ifndef SELECTION_FLAGS
  78. #define SELECTION_FLAGS (lOnlyOne | lNoNilHilite | lDoVAutoscroll)
  79. #endif
  80.  
  81. enum {
  82.     kScrollBarWidth            = 16,
  83.     kScrollBarOffset        = kScrollBarWidth - 1,
  84.     kActiveControl            = 0,                    /* Normal button, no hilite    */
  85.     kDisabledControl        = 255,                    /* Disabled button hilite    */
  86.     kZeroIndent                = 4                        /* Indent if scrollbar == 0    */
  87. };
  88. /*
  89.  * This is a nominal value for the maximum cell width. It might be better as a
  90.  * user-settable parameter.
  91.  */
  92. #define kMaxHorizontalScroll    (CharWidth('M') * 255)
  93.  
  94. /*
  95.  * kAnimationDelay is the number of ticks to display the intermediate
  96.  * "twisting" glyph.
  97.  */
  98. #define kAnimationDelay        3L
  99. /*
  100.  * The triangle gap parameters define the amount of space to display to the
  101.  * left and right of the twist-down triangle. The "outside gap" is to the left
  102.  * on Roman-alphabet scripts and on the right on Hebrew or Arabic scripts.
  103.  * The default values are suitable for small font sizes, but could be increased
  104.  * for large sizes.
  105.  */
  106. #define kTriangleOutsideGap        (1)                    /* From margin to button    */
  107. #define kTriangleInsideGap        (2)                    /* From button to text        */
  108. #define kScrollBarWidth            (16)                /* Width of a scroll bar    */
  109.  
  110. /*
  111.  * Parameters for the print handler.
  112.  */
  113. #define kPrintoutHeaderFont        "\pHelvetica"
  114. #define kPrintoutHeaderFontSize    9
  115. #define kPrintoutHeaderGap        4
  116. #define kPrintoutHeaderStyle    bold
  117.  
  118. /*
  119.  * The List's userHandle contains our private context information. The PolyHandles
  120.  * are used to draw the "triangle" buttons. Note that they are drawn to the list
  121.  * cell height. When it changes, the triangles will be reconstructed. The fontSize,
  122.  * fontNumber, and isLeftJustify variables are used to draw the list cell content.
  123.  */
  124. struct TwistDownPrivateRecord {
  125.         TwistDownDrawProc    drawProc;                /* This draws the cell data    */
  126.         PolyHandle            openTriangle;            /* The "expanded" button    */
  127.         PolyHandle            closedTriangle;            /* The "closed" button        */
  128.         PolyHandle            intermediateTriangle;    /* The "expanding" button    */
  129.         ControlHandle        hScroll;                /* List's horiz. scrollbar    */
  130.         short                tabIndent;                /* NewTwistDownList param    */
  131.         short                fontSize;                /* for TextSize                */
  132.         short                fontNumber;                /* for TextFont                */
  133.         Boolean                canHiliteSelection;        /* TRUE if hilite ok        */
  134.         Boolean                isLeftJustify;            /* GetSysJust value            */
  135.         short                triangleWidth;            /* Twist-down button width    */
  136. };
  137. typedef struct TwistDownPrivateRecord    TwistDownPrivateRecord,
  138.         *TwistDownPrivatePtr, **TwistDownPrivateHdl;
  139.  
  140. /*
  141.  * The de-referenced private data record is too long to type each time it appears
  142.  * so it will be defined by the PRIVATE macro.
  143.  */
  144. #define PRIVATE            (**((TwistDownPrivateHdl) (LIST.userHandle)))
  145. /*
  146.  * These macros simplify access to the flag word in the list element.
  147.  */
  148. #define SetTDFlag(elementHdl, mask)        ((**elementHdl).flag |= (mask))
  149. #define ClearTDFlag(elementHdl, mask)    ((**elementHdl).flag &= ~(mask))
  150. #define InvertTDFlag(elementHdl, mask)    ((**elementHdl).flag ^= (mask))
  151. #define TestTDFlag(elementHdl, mask)    (((**elementHdl).flag & (mask)) != 0)
  152.  
  153. #define SIBLING            (*twistDownSiblingSetPtr)
  154.  
  155. /*
  156.  * The proper way to build a compiled-in LDEF is to plug a transfer address into a
  157.  * stub code resource. The StubRecord must track any changes in the LDEF resource.
  158.  */
  159. #ifdef __powerc
  160. #pragma options align=mac68k
  161. #endif
  162. struct StubRecord {
  163.     long            lea;            /*    Lea            (pc)+8,a0                    */
  164.     short            movea;            /*    Movea.l        (a0),a0                        */
  165.     short            jmp;            /*    jmp            (a0)                        */
  166.     ListDefUPP        ldefAddress;    /*    dc.l        0                            */
  167. };
  168. typedef struct StubRecord    **StubHandle;
  169. #ifdef __powerc
  170. #pragma options align=reset
  171. #endif
  172.  
  173. /*
  174.  * Local (private) functions.
  175.  */
  176. /*
  177.  * Dispose of a PolyHandle. This must be a macro. The argument
  178.  * may not have side-effects.
  179.  */
  180. #define ForgetPoly(thePoly) do {        \
  181.         if (thePoly != NULL) {             \
  182.             KillPoly(thePoly);            \
  183.             thePoly = NULL;                \
  184.         }                                \
  185.     } while (0)
  186. #define height(r)        ((r).bottom - (r).top)
  187. #define width(r)        ((r).right - (r).left)
  188.  
  189. static short                CountVisibleElements(
  190.         TwistDownHdl            twistDownHandle
  191.     );
  192. static void                 ClearSelectedElementBit(
  193.         TwistDownHdl            twistDownHandle
  194.     );
  195. static void                    CopySelectionStateToList(
  196.         ListHandle                theList,
  197.         short                    selectedRow
  198.     );
  199. static void                    SetElementsInList(
  200.         ListHandle                theList,
  201.         TwistDownHdl            twistDownHandle,
  202.         Cell                    *currentCell
  203.     );
  204. static pascal void            TwistDownLDEF(
  205.         short                    listMessage,
  206.         Boolean                    listSelect,
  207.         Rect                    *listRect,
  208.         Cell                    listCell,
  209.         short                    listDataOffset,
  210.         short                    listDataLen,
  211.         ListHandle                listHandle
  212.     );
  213. static void                    DrawTriangle(
  214.         PolyHandle                polyHandle,
  215.         Point                    polyPoint,
  216.         Boolean                    isSelected
  217.     );
  218. static pascal void            DefaultTwistDownDrawProc(
  219.         ListHandle                theList,            /* The list itself            */
  220.         TwistDownPtr            twistDownPtr,        /* Locked data handle        */
  221.         const Rect                *viewRect            /* Draw in this area        */
  222.     );
  223.  
  224. /*
  225.  * Horizontal scrollbar stuff
  226.  */
  227. static void                    AdjustHorizontalScrollbar(
  228.         ListHandle                theList
  229.     );
  230. static pascal void            ScrollTwistDownActionProc(
  231.         register ControlHandle    theControl,
  232.         short                    partCode
  233.     );
  234. static void                    ScrollTwistDownList(
  235.         register ControlHandle    theControl
  236.     );
  237.  
  238. static void                    pstrcpy(
  239.         StringPtr                dst,
  240.         ConstStr255Param        src
  241.     );
  242. static void                    pstrcat(
  243.         StringPtr                dst,
  244.         ConstStr255Param        src
  245.     );
  246.  
  247. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  248.  * NewTwistDownList
  249.  *
  250.  * Create a twist-down list. Before calling, you must set the port to the current
  251.  * window and specify the font and font size that is to be used to draw the list.
  252.  * NewTwistDownList creates an empty one-column list with vertical and horizontal
  253.  * scroll bars. Only one item may be selected at a time; but this could be changed
  254.  * by the application without difficulty. The scrollbars are managed internally.
  255.  *
  256.  * Note: unlike the previous release of TwistDownList, the viewRect does not
  257.  * include the scroll bars. For a fullscreen list, do the following:
  258.  *        viewRect = FrontWindow()->portRect;
  259.  *        viewRect.right -= kScrollBarWidth;
  260.  *        viewRect.bottom -= kScrollBarWidth;
  261.  *
  262.  * The tabIndent parameter should be set to the amount to indent successive levels
  263.  * (zero means no indentation). Setting it to the widMax value from the current
  264.  * font seems reasonable.
  265.  *
  266.  * canHiliteSelection should be TRUE for normal selection (the selection is
  267.  * hilited). It should be FALSE if you want to supress selection. Because
  268.  * hirearchical lists often "select" by revealing a sub-topic, this may be
  269.  * more reasonable in many cases.
  270.  *
  271.  * isLeftJustify should be set TRUE for systems using the Roman alphabet. It would
  272.  * be set FALSE for right-to-left languages such as Arabic and Hebrew. It is used
  273.  * to configure the direction of the buttons and the location of text within
  274.  * the displayed list cell.
  275.  *
  276.  * We manage the horizontal scrollbar by some private trickery -- this lets the
  277.  * list pane be smaller than the amount of text to be displayed. 
  278.  */
  279. pascal ListHandle
  280. NewTwistDownList(
  281.         const Rect                *viewRect,
  282.         TwistDownDrawProc        drawProc,
  283.         unsigned short            tabIndent,
  284.         Boolean                    canHiliteSelection,
  285.         Boolean                    isLeftJustify,
  286.         Boolean                    hasGrowBox
  287.     )
  288. {
  289.         register TwistDownPrivatePtr    privatePtr;
  290.         ListHandle                theList;
  291.         short                    listHeight;
  292.         Point                    cellSize;
  293.         Rect                    dataBounds;
  294.         Rect                    listRect;
  295.         FontInfo                info;
  296.         short                    listFontHeight;
  297.         GrafPtr                    currentPort;
  298.         ListDefUPP                listDefProc;
  299.         StubHandle                stubHandle;
  300.  
  301.         theList = NULL;
  302.         GetPort(¤tPort);
  303.         GetFontInfo(&info);
  304.         listFontHeight = info.ascent + info.descent + info.leading;
  305.         /*
  306.          * Define the list drawing area. If the list viewRect.bottom
  307.          * is less than the portRect.bottom, adjust the list area height
  308.          * integral number of rows will be drawn. If equal, the list
  309.          * area abuts the bottom of the display window and we shouldn't
  310.          * change the bottom or the scroll bars will look wierd.
  311.          */
  312.         listRect = *viewRect;
  313.         SetPt(&cellSize, width(listRect), listFontHeight);
  314.         if ((listRect.bottom + kScrollBarWidth) < currentPort->portRect.bottom) {
  315.             listHeight = height(listRect);
  316.             listHeight -= (listHeight % listFontHeight);
  317.             listRect.bottom = listRect.top + listHeight;
  318.         }
  319.         /*
  320.          * Define a one-column list.
  321.          */
  322.         listDefProc = NewListDefProc(TwistDownLDEF);
  323.         stubHandle = (StubHandle) GetResource('LDEF', LDEF_Stub);
  324.         if (stubHandle == NULL)
  325.             goto exit;                    /* Failure                                */
  326.         (**stubHandle).ldefAddress = listDefProc;
  327.         HNoPurge((Handle) stubHandle);
  328.         SetRect(&dataBounds, 0, 0, 1, 0);
  329.         theList = LNew(
  330.                 &listRect,                /* Viewing area                            */
  331.                 &dataBounds,            /* Rows and col's                        */
  332.                 cellSize,                /* Element size                            */
  333.                 LDEF_Stub,                /* Callback defproc                        */
  334.                 currentPort,            /* Display window                        */
  335.                 TRUE,                    /* Draw it                                */
  336.                 hasGrowBox,                /* Grow box if TRUE                        */
  337.                 TRUE,                    /* Horizontal scroll                    */
  338.                 TRUE                    /* Vertical scroll                        */
  339.             );
  340.         if (theList == NULL)
  341.             goto exit;
  342.         LIST.selFlags = SELECTION_FLAGS;
  343.         LIST.refCon = 0;                /* Paranoia                                */
  344.         LIST.userHandle = NewHandleClear(sizeof (TwistDownPrivateRecord));
  345.         if (LIST.userHandle == NULL)
  346.             goto failure;
  347.         privatePtr = (TwistDownPrivatePtr) (*LIST.userHandle);
  348. #define PRIV (*privatePtr)
  349.         PRIV.drawProc = (drawProc != NULL) ? drawProc : DefaultTwistDownDrawProc;
  350.         PRIV.tabIndent = tabIndent;
  351.         PRIV.canHiliteSelection = canHiliteSelection;
  352.         PRIV.isLeftJustify = isLeftJustify;
  353.         PRIV.fontNumber = currentPort->txFont;
  354.         PRIV.fontSize = currentPort->txSize;
  355.         /*
  356.          * Grab the horizontal scrollbar and clear it from the list record then
  357.          * link the horizontal scrollbar back to the list, and configure it.
  358.          */
  359.         PRIV.hScroll = LIST.hScroll;
  360.         LIST.hScroll = NULL;
  361.         SetControlReference(PRIV.hScroll, (long) theList);
  362.         AdjustHorizontalScrollbar(theList);
  363. #undef PRIV
  364.         CreateTwistDownButtons(theList);
  365.         goto exit;
  366. failure:
  367.         DisposeTwistDownList(theList);
  368.         theList = NULL;
  369. exit:    return (theList);
  370. }
  371.  
  372. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  373.  * DisposeTwistDownList
  374.  *
  375.  * Dispose of the list and our private data.
  376.  */
  377. pascal void
  378. DisposeTwistDownList(
  379.         ListHandle                theList
  380.     )
  381. {
  382.         if (theList != NULL) {
  383.             if (LIST.userHandle != NULL) {
  384.                 LIST.hScroll = PRIVATE.hScroll;
  385.                 ForgetPoly(PRIVATE.openTriangle);
  386.                 ForgetPoly(PRIVATE.closedTriangle);
  387.                 ForgetPoly(PRIVATE.intermediateTriangle);
  388.                 DisposeHandle((Handle) LIST.userHandle);
  389.                 LIST.userHandle = NULL;
  390.             }
  391.             LDispose(theList);
  392.         }
  393. }
  394.  
  395. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  396.  * SetTwistDownListFont
  397.  *
  398.  * Change the display font and font size
  399.  */
  400. pascal void
  401. SetTwistDownListFont(
  402.         ListHandle                theList,
  403.         short                    fontNumber,
  404.         short                    fontSize
  405.     )
  406. {
  407.         FontInfo                info;
  408.         Rect                    listRect;
  409.         Point                    cellSize;
  410.         
  411.         PRIVATE.fontNumber = fontNumber;
  412.         PRIVATE.fontSize = fontSize;
  413.         SetPort(LIST.port);
  414.         TextFont(PRIVATE.fontNumber);
  415.         TextSize(PRIVATE.fontSize);
  416.         GetFontInfo(&info);
  417.         listRect = LIST.rView;
  418.         cellSize.h = width(listRect);
  419.         cellSize.v = info.ascent + info.descent + info.leading;
  420.         LIST.indent.v = info.ascent;
  421.         LCellSize(cellSize, theList);
  422.         CreateTwistDownButtons(theList);
  423.         InvalRect(&listRect);
  424. }
  425.  
  426. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  427.  * UpdateTwistDownList
  428.  * Explicitly update the twistdown list. This is generally called only after
  429.  * a Modal Dialog or Alert obscured the window.
  430.  */
  431. pascal void
  432. UpdateTwistDownList(
  433.         ListHandle                theList
  434.     )
  435. {
  436.         WindowPtr                theWindow;
  437.         GrafPtr                    savePort;
  438.         Rect                    viewRect;
  439.         RgnHandle                listRgn;
  440.         RgnHandle                clipRgn;
  441.         
  442.         if (theList != NULL) {
  443.             theWindow = (WindowPtr) LIST.port;
  444.             if (EmptyRgn(((WindowPeek) theWindow)->updateRgn) == FALSE) {
  445.                 viewRect = LIST.rView;
  446.                 if (LIST.hScroll != NULL || PRIVATE.hScroll != NULL)
  447.                     viewRect.bottom += kScrollBarWidth;
  448.                 if (LIST.vScroll != NULL)
  449.                     viewRect.right += kScrollBarWidth;
  450.                 listRgn = NewRgn();
  451.                 RectRgn(listRgn, &viewRect);
  452.                 SectRgn(listRgn, ((WindowPeek) theWindow)->updateRgn, listRgn);
  453.                 if (EmptyRgn(listRgn) == FALSE) {
  454.                     /*
  455.                      * Fake an update event handler
  456.                      */
  457.                     GetPort(&savePort);
  458.                     SetPort(theWindow);
  459.                     clipRgn = NewRgn();
  460.                     GetClip(clipRgn);
  461.                     SetClip(listRgn);
  462.                     EraseRgn(listRgn);
  463.                     DrawControls(theWindow);
  464.                     DrawGrowIcon(theWindow);
  465.                     InsetRect(&viewRect, -1, -1);
  466.                     FrameRect(&viewRect);
  467.                     LUpdate(listRgn, theList);
  468.                     DisposeRgn(clipRgn);
  469.                     ValidRgn(listRgn);
  470.                     SetPort(savePort);
  471.                 }
  472.                 DisposeRgn(listRgn);
  473.             }
  474.         }
  475. }
  476.  
  477. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  478.  * MoveTwistDownList
  479.  * Move the list within the window.
  480.  */
  481. pascal void
  482. MoveTwistDownList(
  483.         ListHandle                theList,
  484.         short                    leftEdge,
  485.         short                    topEdge
  486.     )
  487. {
  488.         Rect                    viewRect;
  489.         
  490.         if (LIST.rView.left != leftEdge || LIST.rView.top != topEdge) {
  491.             viewRect = LIST.rView;
  492.             InsetRect(&viewRect, -1, -1);
  493.             InvalRect(&viewRect);
  494.             OffsetRect(
  495.                 &LIST.rView,
  496.                 leftEdge - LIST.rView.left,
  497.                 topEdge - LIST.rView.top
  498.             );
  499.             viewRect = LIST.rView;
  500.             InsetRect(&viewRect, -1, -1);
  501.             InvalRect(&viewRect);
  502.             MoveControl(
  503.                 LIST.vScroll,
  504.                 LIST.rView.right - kScrollBarOffset,
  505.                 LIST.rView.top - 1
  506.             );
  507.             MoveControl(
  508.                 PRIVATE.hScroll,
  509.                 LIST.rView.left - 1,
  510.                 LIST.rView.bottom - kScrollBarOffset
  511.             );
  512.         }
  513. }
  514.  
  515. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  516.  * SizeTwistDownList
  517.  * Resize the list. The viewRect is the list rectangle size and does not include
  518.  * the scrollbars. To create a full-screen list, pass the window's portRect with
  519.  * the scrollbars removed.
  520.  */
  521. pascal void
  522. SizeTwistDownList(
  523.         ListHandle                theList,
  524.         short                    newWidth,
  525.         short                    newHeight
  526.     )
  527. {
  528.         Rect                    viewRect;
  529.         Point                    cellSize;
  530.         GrafPtr                    currentPort;
  531.         GrafPtr                    listPort;
  532.  
  533.         GetPort(¤tPort);
  534.         listPort = LIST.port;
  535.         SetPort(listPort);
  536.         viewRect = LIST.rView;
  537.         InsetRect(&viewRect, -1, -1);
  538.         InvalRect(&viewRect);
  539.         /*
  540.          * Put the horizontal scrollbar back into the list record so that
  541.          * the list manager does the resizing for us.
  542.          */
  543.         LIST.hScroll = PRIVATE.hScroll;
  544.         LSize(newWidth, newHeight, theList);
  545.         LIST.hScroll = NULL;
  546.         cellSize = LIST.cellSize;
  547.         cellSize.h = width(LIST.rView);
  548.         LCellSize(cellSize, theList);
  549.         AdjustHorizontalScrollbar(theList);
  550.         viewRect = LIST.rView;
  551.         InsetRect(&viewRect, -1, -1);
  552.         InvalRect(&viewRect);
  553.         SetPort(currentPort);
  554. }
  555.  
  556. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  557.  * CreateTwistDownButtons
  558.  *
  559.  * CreateTwistDownButtons
  560.  * This function creates the three states of the twist-down button that are drawn
  561.  * in the display list.
  562.  *
  563.  * CreateTwistDownButtons is called when the list is created. The application must
  564.  * call it directly after changing the list cell height (by calling LSize).
  565.  *
  566.  * Note that the port and text drawing characteristics must have been set.
  567.  */
  568. pascal void
  569. CreateTwistDownButtons(
  570.         ListHandle                theList
  571.     )
  572. {
  573.         short                    buttonSize;
  574.         short                    halfSize;
  575.         short                    intermediateSize;
  576.         FontInfo                info;
  577.  
  578.         ForgetPoly(PRIVATE.openTriangle);
  579.         ForgetPoly(PRIVATE.closedTriangle);
  580.         ForgetPoly(PRIVATE.intermediateTriangle);
  581.         GetFontInfo(&info);
  582.         buttonSize = info.ascent;                /* The "show sublist" button    */
  583.         buttonSize &= ~1;                        /* Round down to an even number    */
  584.         halfSize = buttonSize / 2;
  585.         intermediateSize = (buttonSize * 3) / 4;
  586.         PRIVATE.openTriangle = OpenPoly();
  587.             MoveTo(0, halfSize);
  588.             LineTo(buttonSize, halfSize);
  589.             LineTo(halfSize, buttonSize);
  590.             LineTo(0, halfSize);
  591.         ClosePoly();
  592.         if (PRIVATE.isLeftJustify) {            /* Roman alphabet triangles        */
  593.             PRIVATE.closedTriangle = OpenPoly();
  594.                 MoveTo(halfSize, 0);
  595.                 LineTo(buttonSize, halfSize);
  596.                 LineTo(halfSize, buttonSize);
  597.                 LineTo(halfSize, 0);
  598.             ClosePoly();
  599.             PRIVATE.intermediateTriangle = OpenPoly();    
  600.                 MoveTo(intermediateSize, 0);
  601.                 LineTo(intermediateSize, intermediateSize);
  602.                 LineTo(0, intermediateSize);
  603.                 LineTo(intermediateSize, 0);
  604.             ClosePoly();
  605.         }
  606.         else {                                    /* Arabic/Hebrew triangles        */
  607.             PRIVATE.closedTriangle = OpenPoly();
  608.                 MoveTo(buttonSize - halfSize, 0);
  609.                 LineTo(0, halfSize);
  610.                 LineTo(buttonSize - halfSize, buttonSize);
  611.                 LineTo(buttonSize - halfSize, 0);
  612.             ClosePoly();
  613.             PRIVATE.intermediateTriangle = OpenPoly();
  614.                 MoveTo(buttonSize - intermediateSize, 0);
  615.                 LineTo(buttonSize - intermediateSize, intermediateSize);
  616.                 LineTo(buttonSize, intermediateSize);
  617.                 LineTo(buttonSize - intermediateSize, 0);
  618.             ClosePoly();
  619.         }        
  620.         /*
  621.          * Remember the width of the "button" area.
  622.          */
  623.         PRIVATE.triangleWidth =
  624.                     (**PRIVATE.openTriangle).polyBBox.right
  625.                     + kTriangleOutsideGap
  626.                     + kTriangleInsideGap;
  627. }
  628.  
  629. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  630.  * DoTwistDownClick
  631.  *
  632.  * DoTwistDownClick handles all processing after a click in the list window.
  633.  * It returns an indication of the user action:
  634.  *    kTwistDownNotInList
  635.  *        The click was not in this list. Your application may ignore this click or
  636.  *        take other appropriate action.
  637.  *    kTwistDownNoClick
  638.  *        The user released the mouse outside of the list area: this click should be
  639.  *        ignored. (It may have been a click in the scroll bar.)
  640.  *    kTwistDownButtonClick
  641.  *        The user clicked on the twist-down button. The list will be expanded or
  642.  *        contracted as appropriate.
  643.  *    kTwistDownClick
  644.  *        The user clicked (once) on a list datum. The application should treat this
  645.  *        as an item selection.
  646.  *    kTwistDownDoubleClick
  647.  *        The user double-clicked on a list datum. The application should open this
  648.  *        item or take other appropriate action.
  649.  *
  650.  * If DoTwistDownClick returns kTwistDownButtonClick, kTwistDownClick, or
  651.  * kTwistDownDoubleClick, selectedListCell will be set to the cell that the user
  652.  * clicked on.
  653.  */
  654. pascal TwistDownClickState
  655. DoTwistDownClick(
  656.         ListHandle                theList,
  657.         const EventRecord        *eventRecordPtr,
  658.         Cell                    *selectedListCell
  659.     )
  660. {
  661.         Cell                    theCell;
  662.         Rect                    hitRect;
  663.         Boolean                    inHitRect;
  664.         Boolean                    newInHitRect;
  665.         short                    cellHeight;
  666.         short                    visibleTop;
  667.         TwistDownHdl            twistDownHandle;
  668.         Point                    mousePt;
  669.         TwistDownClickState        result;
  670.         long                    finalTicks;
  671.         short                    part;
  672.         ControlHandle            theControl;
  673.         static ControlActionUPP    controlActionUPP;
  674.         
  675.         mousePt = eventRecordPtr->where;
  676.         GlobalToLocal(&mousePt);
  677.         /*
  678.          * We handle the horizontal scrollbar ourselves -- and do not pass
  679.          * these clicks to LClick as the scrollbar isn't there anymore.
  680.          */
  681.         hitRect = (**PRIVATE.hScroll).contrlRect;
  682.         if (PtInRect(mousePt, &hitRect)) {
  683.             part = FindControl(
  684.                         mousePt,
  685.                         (**PRIVATE.hScroll).contrlOwner,
  686.                         &theControl
  687.                     );
  688.             if (part >= 0 && theControl == PRIVATE.hScroll) {
  689.                 if (part == kInIndicatorControlPart) {
  690.                     if (TrackControl(theControl, mousePt, NULL))
  691.                         ScrollTwistDownList(theControl);
  692.                 }
  693.                 else {
  694.                     if (controlActionUPP == NULL) {
  695.                         controlActionUPP = NewControlActionProc(
  696.                                     ScrollTwistDownActionProc
  697.                                 );
  698.                     }
  699.                     TrackControl(theControl, mousePt, controlActionUPP);
  700.                 }
  701.             }        
  702.         }
  703.         else {
  704.             hitRect = LIST.rView;
  705.             hitRect.right += kScrollBarWidth;
  706.             if (PtInRect(mousePt, &hitRect) == FALSE)
  707.                 result = kTwistDownNotInList;
  708.             else {
  709.                 /*
  710.                  * Set hitRect to the area of the list that contains the
  711.                  * twist-down triangle button. Note that this presumes a list
  712.                  * where only column zero is displayed.
  713.                  */
  714.                 if (PRIVATE.isLeftJustify) {
  715.                     hitRect.right = LIST.rView.left
  716.                             + LIST.indent.h
  717.                             + PRIVATE.triangleWidth;
  718.                 }
  719.                 else {
  720.                     hitRect.left = LIST.rView.right
  721.                             - LIST.indent.h
  722.                             - PRIVATE.triangleWidth;
  723.                 }
  724.                 inHitRect = FALSE;
  725.                 if (PtInRect(mousePt, &hitRect)) {
  726.                     /*
  727.                      * It's in a the triangle area. Find the selected cell and
  728.                      * check whether this cell's element has a visible button.
  729.                      */
  730.                     visibleTop = LIST.visible.top;
  731.                     cellHeight = LIST.cellSize.v;
  732.                     theCell.h = 0;        /* This has the visual content            */
  733.                     theCell.v =
  734.                         ((mousePt.v - LIST.rView.top) / cellHeight)
  735.                         + visibleTop;
  736.                     /*
  737.                      * Set inHitRect TRUE if there is a sub-list button here.\
  738.                      * Note: it is possible to have a button but no actual
  739.                      * sub-list (consider an empty folder in a disk hierarchy: the
  740.                      * presence of the button tells the user "it's a folder").
  741.                      */
  742.                     twistDownHandle = GetTwistDownElementHandle(theList, theCell);
  743.                     if (twistDownHandle != NULL
  744.                      && TestTDFlag(twistDownHandle, kHasTwistDown))
  745.                         inHitRect = TRUE;
  746.                 }
  747.                 if (inHitRect == FALSE) {
  748.                     /*
  749.                      * This cell doesn't have an expansion triangle, or the user
  750.                      * did not click in the button area. Just call the  normal
  751.                      * list click handler to manage the scroll bars. Set result
  752.                      * appropriately.
  753.                      */
  754.                     result = (LClick(mousePt, eventRecordPtr->modifiers, theList))
  755.                         ? kTwistDownDoubleClick
  756.                         : kTwistDownClick;
  757.                 }
  758.                 else {
  759.                     /*
  760.                      * The user clicked on in the twist-down button area. Simulate
  761.                      * a button click and track the mouse as it wanders in and out
  762.                      * of the button area. (inHitRect is true at this point).
  763.                      * Whenever the button selection state changes, call LDraw to
  764.                      * redraw the twist-down triangle. This is the way to simulate
  765.                      * TrackControl.
  766.                      */
  767.                     SetTDFlag(twistDownHandle,
  768.                         (kDrawButtonFilled | kOnlyRedrawButton));
  769.                     LDraw(theCell, theList);
  770.                     /*
  771.                      * Set hitRect to the dimensions of the twist-down button.
  772.                      */
  773.                     hitRect.top =
  774.                         ((theCell.v - visibleTop) * cellHeight)
  775.                         + LIST.rView.top;
  776.                     hitRect.bottom = hitRect.top + cellHeight;
  777.                     /*
  778.                      * Track the mouse while it still down: if it moves into the
  779.                      * triangle rectangle, redraw it filled, if it moves out of
  780.                      * the triangle, redraw it unfilled.
  781.                      */
  782.                     if (StillDown()) {
  783.                         while (WaitMouseUp()) {
  784.                             GetMouse(&mousePt);
  785.                             newInHitRect = PtInRect(mousePt, &hitRect);
  786.                             if (newInHitRect != inHitRect) {
  787.                                 /*
  788.                                  * The mouse moved in or out of the triangle.
  789.                                  */
  790.                                 InvertTDFlag(twistDownHandle, kDrawButtonFilled);
  791.                                 LDraw(theCell, theList);
  792.                                 inHitRect = newInHitRect;
  793.                             }
  794.                         }
  795.                     }
  796.                     /*
  797.                      * The user released the mouse.
  798.                      */
  799.                     if (inHitRect == FALSE) {
  800.                         /*
  801.                          * Normally, drawButtonFilled will be clear. It can be set,
  802.                          * however, if the user clicks so briefly on the triangle
  803.                          * that the StillDown() test above is FALSE.
  804.                          */
  805.                         if (TestTDFlag(twistDownHandle, kDrawButtonFilled)) {
  806.                             ClearTDFlag(twistDownHandle, kDrawButtonFilled);
  807.                             LDraw(theCell, theList);
  808.                         }
  809.                         result = kTwistDownNoClick;
  810.                     }
  811.                     else {
  812.                         /*
  813.                          * The user released the mouse in the expansion triangle.
  814.                          * Draw an intermediate "animation" triangle. Then call 
  815.                          * ExpandOrCollapseTwistDownList which will redraw the
  816.                          * button in its new state.
  817.                          */
  818.                         SetTDFlag(twistDownHandle,
  819.                             (kDrawIntermediate | kEraseButtonArea));
  820.                         LDraw(theCell, theList);
  821.                         Delay(kAnimationDelay, &finalTicks);
  822.                         ClearTDFlag(
  823.                             twistDownHandle,
  824.                             (  kDrawIntermediate
  825.                              | kDrawButtonFilled
  826.                              | kEraseButtonArea
  827.                             )
  828.                         );
  829.                         ExpandOrCollapseTwistDownList(theList, theCell);
  830.                         result = kTwistDownButtonClick;
  831.                         *selectedListCell = theCell;
  832.                     }
  833.                     ClearTDFlag(twistDownHandle, kOnlyRedrawButton);
  834.                 }
  835.             }
  836.         }
  837.         return (result);
  838. }
  839.  
  840. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  841.  * ExpandOrCollapseTwistDownList
  842.  *
  843.  * ExpandOrCollapseTwistDownList modifies the "show sublist" flag for the selected
  844.  * cell and rebuilds the visual display.
  845.  */
  846. pascal void
  847. ExpandOrCollapseTwistDownList(
  848.         ListHandle                theList,
  849.         Cell                    selectedListCell
  850.     )
  851. {
  852.         TwistDownHdl            twistDownHandle;
  853.         
  854.         twistDownHandle = GetTwistDownElementHandle(theList, selectedListCell);
  855.         if (twistDownHandle != NULL
  856.          && TestTDFlag(twistDownHandle, kHasTwistDown)) {
  857.             InvertTDFlag(twistDownHandle, kShowSublist);
  858.             /*
  859.              * Redraw the twist-down button in its new state.
  860.              */
  861.             ClearTDFlag(twistDownHandle, kDrawButtonFilled);
  862.             SetTDFlag(twistDownHandle, (kOnlyRedrawButton | kEraseButtonArea));
  863.             LDraw(selectedListCell, theList);
  864.             ClearTDFlag(twistDownHandle, (kOnlyRedrawButton | kEraseButtonArea));
  865.             /*
  866.              * If some other part of the list will change, rebuild the
  867.              * List Manager list cells and redraw the list.
  868.              */
  869.             if ((**twistDownHandle).subElement != NULL)
  870.                 BuildVisibleList(theList, selectedListCell.v);
  871.         }
  872. }
  873.  
  874. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  875.  * CreateVisibleList
  876.  *
  877.  * CreateVisibleList is called when the list is created. It stores the list
  878.  * head into cell [0, 0] and calls BuildVisibleList to instantiate the display.
  879.  */
  880. pascal void
  881. CreateVisibleList(
  882.         ListHandle                theList,
  883.         TwistDownHdl            twistDownHandle
  884.     )
  885. {
  886.         Cell                    theCell;
  887.         
  888.         if (LIST.dataBounds.bottom == 0) {
  889.             /*
  890.              * Add one row to the list so there is a place for the head element.
  891.              */
  892.             LSetDrawingMode(FALSE, theList);
  893.             LAddRow(1, 0, theList);
  894.             LSetDrawingMode(TRUE, theList);
  895.         }
  896.         SetPt(&theCell, 0, 0);
  897.         LSetCell(&twistDownHandle, sizeof twistDownHandle, theCell, theList);
  898.         BuildVisibleList(theList, 0);
  899. }
  900.  
  901. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  902.  * BuildVisibleList
  903.  *
  904.  * BuildVisibleList is called when the list is created, or when the user clicks
  905.  * on a twist-down button. selectedRow is the first row that needs to be redrawn.
  906.  * To rebuild the entire list, store the list head into cell [0, 0] and call
  907.  * with selectedRow = 0. Note: because people can be rebuilding the list from
  908.  * within a larger sublist context, the list head must be stored in cell [0, 0].
  909.  */
  910. pascal void
  911. BuildVisibleList(
  912.         ListHandle                theList,
  913.         short                    selectedRow
  914.     )
  915. {
  916.         short                    nRows;            /* How many we need    to show        */
  917.         short                    currentRows;    /* How many are in the list        */
  918.         Rect                    viewRect;
  919.         TwistDownHdl            listHead;
  920.         Cell                    theCell;
  921.         
  922.         LSetDrawingMode(FALSE, theList);
  923.         SetPt(&theCell, 0, 0);                        /* Get the list head        */
  924.         listHead = GetTwistDownElementHandle(theList, theCell);
  925.         ClearSelectedElementBit(listHead);
  926.         CopySelectionStateToList(theList, selectedRow);
  927.         nRows = CountVisibleElements(listHead);
  928.         currentRows = LIST.dataBounds.bottom;
  929.         if (currentRows > nRows)
  930.             LDelRow(currentRows - nRows, nRows, theList);    /* Shrink the list    */
  931.         else if (currentRows < nRows)
  932.             LAddRow(nRows - currentRows, currentRows + 1, theList);    /* Grow it    */
  933.         if (nRows != 0)
  934.             SetElementsInList(theList, listHead, &theCell);
  935.         LSetDrawingMode(TRUE, theList);
  936.         /*
  937.          * Redraw any elements that are greater than the inserted row.
  938.          */
  939.         viewRect = LIST.rView;
  940.         viewRect.top += ((selectedRow + 1) - LIST.visible.top)
  941.                     * LIST.cellSize.v;
  942.         if (viewRect.top < viewRect.bottom)
  943.             InvalRect(&viewRect);
  944. }
  945.  
  946. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  947.  * CountVisibleElements
  948.  *
  949.  * CountVisibleElements is a recursive function that returns the number of visible
  950.  * elements in the list. Call with the list head to process the entire list. Note:
  951.  * list elements are visible, but sub-lists are visible only if the button state
  952.  * requests visiblity. This function may be called with a NULL argument without
  953.  * problems. Also note that we ignore the current List Manager cell data, working
  954.  * only with the actual linked list structure. By convention, however, the list
  955.  * head is stored in cell [0, 0].
  956.  */
  957. static short
  958. CountVisibleElements(
  959.         TwistDownHdl            twistDownHandle
  960.     )
  961. {
  962.         short                    result;
  963.  
  964.         result = 0;
  965.         while (twistDownHandle != NULL) {
  966.             ++result;
  967.             if (TestTDFlag(twistDownHandle, kShowSublist))
  968.                 result += CountVisibleElements((**twistDownHandle).subElement);
  969.             twistDownHandle = (**twistDownHandle).nextElement;
  970.         }            
  971.         return (result);
  972. }
  973.  
  974. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  975.  * CountListElements
  976.  *
  977.  * CountListElements returns the number of elements in the list: it counts all
  978.  * elements in all sub-lists, visible or not.
  979.  */
  980. pascal unsigned long
  981. CountListElements(
  982.         TwistDownHdl            twistDownHandle
  983.     )
  984. {
  985.         unsigned long            result;
  986.  
  987.         result = 0;
  988.         while (twistDownHandle != NULL) {
  989.             ++result;
  990.             if ((**twistDownHandle).subElement != NULL)
  991.                 result += CountVisibleElements((**twistDownHandle).subElement);
  992.             twistDownHandle = (**twistDownHandle).nextElement;
  993.         }            
  994.         return (result);
  995. }
  996.  
  997. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  998.  * ClearSelectedElementBit
  999.  *
  1000.  * When we expand or contract the visible list, the relationship between the
  1001.  * visual display and the actual data changes. In particular, the selected cell
  1002.  * may contain different data. To resolve this dilemna, we copy the current
  1003.  * selection from the display list to the data elements, then copy the information
  1004.  * back when the new elements are inserted into the list. This is a three-step
  1005.  * process. First, we clear out the kSelectedElement bit from the linked list.
  1006.  */
  1007. static void
  1008. ClearSelectedElementBit(
  1009.         TwistDownHdl            twistDownHandle
  1010.     )
  1011. {
  1012.         while (twistDownHandle != NULL) {
  1013.             ClearTDFlag(twistDownHandle, kSelectedElement);
  1014.             if ((**twistDownHandle).subElement != NULL)
  1015.                 ClearSelectedElementBit((**twistDownHandle).subElement);
  1016.             twistDownHandle = (**twistDownHandle).nextElement;
  1017.         }            
  1018. }
  1019.  
  1020. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  1021.  * CopySelectionStateToList
  1022.  *
  1023.  * After clearing the kSelectedElementBit from the element list, we save the
  1024.  * "is selected" bit from the display list into the associated element. Note that
  1025.  * the cell will be deselected but selection state is restored when the list is
  1026.  * rebuilt. The function starts with the row after the selected (clicked on) cell
  1027.  * as that cell is not redrawn and, presumably, is correctly hilited.
  1028.  */
  1029. static void
  1030. CopySelectionStateToList(
  1031.         ListHandle                theList,
  1032.         short                    selectedRow
  1033.     )
  1034. {
  1035.         TwistDownHdl            twistDownHandle;
  1036.         Cell                    theCell;
  1037.         
  1038.         SetPt(&theCell, 0, selectedRow + 1);
  1039.         while (LGetSelect(TRUE, &theCell, theList)) {
  1040.             twistDownHandle = GetTwistDownElementHandle(theList, theCell);
  1041.             if (twistDownHandle != NULL)
  1042.                 SetTDFlag(twistDownHandle, kSelectedElement);
  1043.             LSetSelect(FALSE, theCell, theList);
  1044.             ++theCell.v;
  1045.         }
  1046. }
  1047.  
  1048. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  1049.  * SetElementsInList
  1050.  *
  1051.  * This is a recursive function that copies visible list elements from the linked
  1052.  * list to the List Manager list. The List Manager list was extended so it holds
  1053.  * all visible cells. Note that currentCell is a "global" that always contains
  1054.  * the current List Manager cell. To process the entire list, set currentCell to
  1055.  * [0, 0] and call with the list head. If the list element is selected (from
  1056.  * CopySelectionStateToList above), select this list cell.
  1057.  */ 
  1058. static void
  1059. SetElementsInList(
  1060.         ListHandle                theList,
  1061.         TwistDownHdl            twistDownHandle,
  1062.         Cell                    *currentCell
  1063.     )
  1064. {
  1065.         while (twistDownHandle != NULL) {
  1066.             LSetCell(
  1067.                 &twistDownHandle, sizeof twistDownHandle, *currentCell, theList);
  1068.             if (TestTDFlag(twistDownHandle, kSelectedElement))
  1069.                 LSetSelect(TRUE, *currentCell, theList);
  1070.             ++currentCell->v;
  1071.             if (TestTDFlag(twistDownHandle, kShowSublist)) {
  1072.                 SetElementsInList(
  1073.                     theList,
  1074.                     (**twistDownHandle).subElement,
  1075.                     currentCell
  1076.                 );
  1077.             }
  1078.             twistDownHandle = (**twistDownHandle).nextElement;
  1079.         }
  1080. }
  1081.  
  1082. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  1083.  * GetTwistDownElementHandle
  1084.  *
  1085.  * GetTwistDownElementHandle returns the TwistDownHdl that is stored in a List
  1086.  * cell. It will return NULL if the cell is out of bounds.
  1087.  */
  1088. pascal TwistDownHdl
  1089. GetTwistDownElementHandle(
  1090.         ListHandle                theList,
  1091.         Cell                    theCell
  1092.     )
  1093. {
  1094.         TwistDownHdl            twistDownHandle;
  1095.         short                    dataSize;
  1096.         
  1097.         dataSize = sizeof twistDownHandle;
  1098.         LGetCell(&twistDownHandle, &dataSize, theCell, theList);
  1099.         if (dataSize != sizeof twistDownHandle)
  1100.             twistDownHandle = NULL;
  1101.         return (twistDownHandle);
  1102. }
  1103.  
  1104. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  1105.  * NewTwistDownSiblingSet
  1106.  *
  1107.  * Initialize a sibling set.
  1108.  */
  1109. pascal void
  1110. NewTwistDownSiblingSet(
  1111.         TwistDownSiblingSetPtr    twistDownSiblingSetPtr
  1112.     )
  1113. {
  1114.         SIBLING.thisElement = NULL;
  1115.         SIBLING.firstElement = NULL;
  1116.         SIBLING.previousElement = NULL;
  1117. }
  1118.  
  1119. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  1120.  * MakeTwistDownSibling
  1121.  *
  1122.  * Add a new element (by calling MakeTwistDownElement) to the end of the current
  1123.  * linked list. Also, handle the bookkeeping needed for the first element.
  1124.  */
  1125. pascal OSErr
  1126. MakeTwistDownSibling(
  1127.         TwistDownSiblingSetPtr    twistDownSiblingSetPtr,
  1128.         short                    indentLevel,
  1129.         unsigned short            dataLength,
  1130.         const Ptr                dataPtr
  1131.     )
  1132. {
  1133.         OSErr                    status;
  1134.         
  1135.         status = MakeTwistDownElement(
  1136.                     SIBLING.previousElement,
  1137.                     indentLevel,
  1138.                     dataLength,
  1139.                     dataPtr,
  1140.                     &SIBLING.thisElement
  1141.                 );
  1142.         if (status == noErr) {
  1143.             SIBLING.previousElement = SIBLING.thisElement;
  1144.             if (SIBLING.firstElement == NULL)
  1145.                 SIBLING.firstElement = SIBLING.thisElement;
  1146.         }
  1147.         return (status);
  1148. }
  1149.  
  1150. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  1151.  * MakeTwistDownElement
  1152.  *
  1153.  * Adds an element to a linked list. It is designed to create elements in a
  1154.  * hierarchical linked-list where each element may be followed by a successor
  1155.  * (on the same "level") and/or by a child list (on a lower "level"). Note that
  1156.  * MakeTwistDownElement is only concerned with the linked list. It does not
  1157.  * change the List Manager list.
  1158.  *
  1159.  * The parameters are as follows:
  1160.  *    previousElement
  1161.  *        This is a handle to the predecessor to this element. previousElement is
  1162.  *        NULL If this is the first element in a list or the first on a level.
  1163.  *    indentLevel
  1164.  *        This is the indentation-level of this list element. It is only
  1165.  *        used to tab the list elements on the visual display. If you don't
  1166.  *        want tabbing, set tabIndent to zero when the list was created.
  1167.  *    dataLength
  1168.  *        This is the length of the list element datum.
  1169.  *    dataPtr
  1170.  *        This is a pointer to the first byte of the list element datum. If NULL,
  1171.  *        a data block of the requisite size will be created, but the caller is
  1172.  *        responsible for filling it in.
  1173.  *    result
  1174.  *        If MakeElement succeeds, result will will contain a handle to the
  1175.  *        list element it created. This is needed to create a successor
  1176.  *        element.
  1177.  */
  1178. pascal OSErr
  1179. MakeTwistDownElement(
  1180.         TwistDownHdl            previousElement,
  1181.         short                    indentLevel,
  1182.         unsigned short            dataLength,
  1183.         const Ptr                dataPtr,
  1184.         TwistDownHdl            *result
  1185.     )
  1186. {
  1187.         TwistDownHdl            twistDownHandle;
  1188.         
  1189.         twistDownHandle = (TwistDownHdl) NewHandle(
  1190.                     sizeof (TwistDownRecord)
  1191.                     - sizeof (unsigned char)
  1192.                     + dataLength
  1193.                 );
  1194.         if (twistDownHandle != NULL) {
  1195.             if (previousElement != NULL)
  1196.                 (**previousElement).nextElement = twistDownHandle;
  1197.             (**twistDownHandle).nextElement = NULL;
  1198.             (**twistDownHandle).subElement = NULL;
  1199.             (**twistDownHandle).flag = 0;
  1200.             (**twistDownHandle).indentLevel = indentLevel;
  1201.             (**twistDownHandle).dataLength    = dataLength;
  1202.             if (dataPtr != NULL)
  1203.                 BlockMoveData(dataPtr, (**twistDownHandle).data, dataLength);
  1204.             *result = twistDownHandle;
  1205.         }
  1206.         return (MemError());
  1207. }
  1208.  
  1209. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  1210.  * DisposeTwistDownHdl
  1211.  *
  1212.  * Dispose of the linked list that has the argument at its head, and of all
  1213.  * sublists linked to this list. Note that it presumes that the list element does
  1214.  * not, itself, contain Ptr or Handle data that must be disposed. The userProc,
  1215.  * if provided, will be called on each element.
  1216.  */
  1217. pascal void
  1218. DisposeTwistDownHdl(
  1219.         TwistDownHdl            twistDownHandle,
  1220.         DisposeTwistDownCallback userProc,
  1221.         void                    *userData
  1222.     )
  1223. {
  1224.         TwistDownHdl            nextElement;
  1225.         TwistDownHdl            subElement;
  1226.         
  1227.         while (twistDownHandle != NULL) {
  1228.             nextElement = (**twistDownHandle).nextElement;
  1229.             subElement = (**twistDownHandle).subElement;
  1230.             if (userProc != NULL)
  1231.                 (*userProc)(twistDownHandle, userData);
  1232.             DisposeHandle((Handle) twistDownHandle);
  1233.             if (subElement != NULL)
  1234.                 DisposeTwistDownHdl(subElement, userProc, userData);
  1235.             twistDownHandle = nextElement;
  1236.         }
  1237. }
  1238.  
  1239. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  1240.  * AppendTwistDownList
  1241.  *
  1242.  * Append the second list to the first list. Return the first list.
  1243.  */
  1244. pascal TwistDownHdl
  1245. AppendTwistDownList(
  1246.         TwistDownHdl            dstTwistDownHdl,
  1247.         TwistDownHdl            srcTwistDownHdl
  1248.     )
  1249. {
  1250.         TwistDownHdl            result;
  1251.         
  1252.         if ((result = dstTwistDownHdl) == NULL)
  1253.             result = srcTwistDownHdl;
  1254.         else {
  1255.             while ((**dstTwistDownHdl).nextElement != NULL)
  1256.                 dstTwistDownHdl = (**dstTwistDownHdl).nextElement;
  1257.             (**dstTwistDownHdl).nextElement = srcTwistDownHdl;
  1258.         }
  1259.         return (result);
  1260. }
  1261.  
  1262. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  1263.  * TwistDownLDEF
  1264.  *
  1265.  * Draw the twist-down list cell. Note that we have to draw the expansion button
  1266.  * in one of five states: (expanded/compressed), (normal/selected), and an
  1267.  * animation state.
  1268.  */
  1269. pascal void
  1270. TwistDownLDEF(
  1271.         short                    listMessage,
  1272.         Boolean                    listSelect,
  1273.         Rect                    *listRect,
  1274.         Cell                    listCell,
  1275.         short                    listDataOffset,
  1276.         short                    listDataLen,
  1277.         ListHandle                theList
  1278.     )
  1279. {
  1280.         short                    indent;
  1281.         TwistDownHdl            twistDownHandle;
  1282.         register TwistDownPtr    twistDownPtr;
  1283.         GrafPtr                    grafPtr;
  1284.         short                    saveFontNumber;
  1285.         short                    saveFontSize;
  1286.         short                    cellSize;
  1287.         PolyHandle                polyHandle;
  1288.         Point                    polyPoint;
  1289.         Rect                    viewRect;
  1290.         signed char                elementLockState;
  1291.         Boolean                    onlyRedrawButton;
  1292.         FontInfo                info;
  1293. #define TestFlag(flagBit)    (((*twistDownPtr).flag & (flagBit)) != 0)
  1294.  
  1295.         UNUSED(listDataOffset);
  1296.         switch (listMessage) {
  1297.         case lInitMsg:
  1298.             /*
  1299.              * Initialize the list indentation values. Since the userHandle
  1300.              * (which has the TwistDown private data) hasn't been setup yet,
  1301.              * we let the current font and font size establish the indentation.
  1302.              */
  1303.             LIST.indent.h = 4;
  1304.             GetFontInfo(&info);
  1305.             LIST.indent.v = info.ascent;
  1306.             break;
  1307.         case lCloseMsg:
  1308.             if (LIST.userHandle != NULL) {
  1309.                 ForgetPoly(PRIVATE.openTriangle);
  1310.                 ForgetPoly(PRIVATE.closedTriangle);
  1311.                 ForgetPoly(PRIVATE.intermediateTriangle);
  1312.                 DisposeHandle(LIST.userHandle);
  1313.                 LIST.userHandle = NULL;
  1314.             }
  1315.             break;
  1316.         case lDrawMsg:
  1317.             onlyRedrawButton = FALSE;
  1318.             if (listDataLen > 0 && LIST.userHandle != NULL) {
  1319.                 /*
  1320.                  * Get the cell content. This is the handle that has the list
  1321.                  * element. We check that the userHandle has been setup correctly.
  1322.                  * Note that we don't use LFind (or similar) because the data
  1323.                  * might not be aligned in the list cell storage. Actually, the
  1324.                  * data is aligned as we only store Handles in the cells.
  1325.                  */
  1326.                 cellSize = sizeof twistDownHandle;
  1327.                 LGetCell(&twistDownHandle,    &cellSize, listCell, theList);
  1328.                 if (cellSize == sizeof twistDownHandle
  1329.                  && twistDownHandle != NULL) {
  1330.                     elementLockState = HGetState((Handle) twistDownHandle);
  1331.                     HLock((Handle) twistDownHandle);
  1332.                     twistDownPtr = (*twistDownHandle);
  1333.                     onlyRedrawButton = TestFlag(kOnlyRedrawButton);
  1334.                     viewRect = *listRect;
  1335.                     if (onlyRedrawButton) {
  1336.                         if (PRIVATE.isLeftJustify) {
  1337.                             viewRect.right = viewRect.left
  1338.                                         + LIST.indent.h
  1339.                                         + PRIVATE.triangleWidth;
  1340.                         }
  1341.                         else {
  1342.                             viewRect.left = viewRect.right
  1343.                                     - LIST.indent.h
  1344.                                     - kTriangleOutsideGap
  1345.                                     - PRIVATE.triangleWidth;
  1346.                         }
  1347.                     }
  1348.                     if (onlyRedrawButton == FALSE || TestFlag(kEraseButtonArea))
  1349.                         EraseRect(&viewRect);
  1350.                     if (TestFlag(kHasTwistDown)) {
  1351.                         /*
  1352.                          * Draw the expansion triangle in one of
  1353.                          * its three states.
  1354.                          */
  1355.                         polyPoint.v = listRect->top + 1;
  1356.                         if (PRIVATE.isLeftJustify) {
  1357.                             polyPoint.h = listRect->left
  1358.                                         + LIST.indent.h
  1359.                                         + kTriangleOutsideGap;
  1360.                         }
  1361.                         else {
  1362.                             polyPoint.h = listRect->right
  1363.                                         - LIST.indent.h
  1364.                                         - PRIVATE.triangleWidth
  1365.                                         + kTriangleInsideGap;
  1366.                         }
  1367.                         if (TestFlag(kDrawIntermediate))
  1368.                             polyHandle = PRIVATE.intermediateTriangle;
  1369.                         else if (TestFlag(kShowSublist))
  1370.                             polyHandle = PRIVATE.openTriangle;
  1371.                         else {
  1372.                             polyHandle = PRIVATE.closedTriangle;
  1373.                         }
  1374.                         DrawTriangle(
  1375.                             polyHandle,
  1376.                             polyPoint,
  1377.                             TestFlag(kDrawButtonFilled)
  1378.                         );
  1379.                     }
  1380.                     if (onlyRedrawButton == FALSE
  1381.                      && (*twistDownPtr).dataLength > 0) {
  1382.                         /*
  1383.                          * Indent the text to show the depth of the hierarchy.
  1384.                          */
  1385.                         indent = LIST.indent.h
  1386.                             + PRIVATE.triangleWidth
  1387.                             + (PRIVATE.tabIndent * (*twistDownPtr).indentLevel);
  1388.                         viewRect = *listRect;
  1389.                         /*
  1390.                          * Build a display rectangle for the cell text and set the
  1391.                          * pen to the leftmost position of the text. Note that
  1392.                          * this right-justifies text for Arabic and Hebrew.
  1393.                          */
  1394.                         if (PRIVATE.isLeftJustify)
  1395.                             viewRect.left += indent;
  1396.                         else {
  1397.                             viewRect.right -= indent;
  1398.                         }
  1399.                         GetPort(&grafPtr);
  1400.                         saveFontNumber = grafPtr->txFont;
  1401.                         saveFontSize = grafPtr->txSize;
  1402.                         TextFont(PRIVATE.fontNumber);
  1403.                         TextSize(PRIVATE.fontSize);
  1404.                         (PRIVATE.drawProc)(theList, twistDownPtr, &viewRect);
  1405.                         TextFont(saveFontNumber);
  1406.                         TextSize(saveFontSize);
  1407.                     }                            /* Drawing cell                    */
  1408.                     HSetState((Handle) twistDownHandle, elementLockState);
  1409.                 }                                /* Have list element            */
  1410.             }                                    /* Have cell data                */
  1411.             if (listSelect == FALSE || onlyRedrawButton)
  1412.                 break;
  1413.             /* Continue to do hilite */
  1414.         case lHiliteMsg:
  1415.             if (PRIVATE.canHiliteSelection) {
  1416.                 LMSetHiliteMode(LMGetHiliteMode() & ~(1 << hiliteBit));
  1417.                 viewRect = *listRect;
  1418.                 if (PRIVATE.isLeftJustify)
  1419.                     viewRect.left += (LIST.indent.h * PRIVATE.triangleWidth);
  1420.                 else {
  1421.                     viewRect.right -= (LIST.indent.h * PRIVATE.triangleWidth);
  1422.                 }
  1423.                 InvertRect(&viewRect);
  1424.             }
  1425.             break;
  1426.         }
  1427. #undef TestFlag
  1428. }
  1429.  
  1430. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  1431.  * DrawTriangle
  1432.  *
  1433.  * Draw the polygon and fill it so it looks a bit like the Finder. If isSelected
  1434.  * is TRUE, the polygon is always black-filled. Else, on color monitors, a light
  1435.  * gray is chosen.
  1436.  *
  1437.  * DrawTriangle uses the DeviceLoop available with System 7 to draw across
  1438.  * multiple screens.
  1439.  */
  1440.  
  1441. typedef struct TriangleInfo {
  1442.     PolyHandle        polyHandle;
  1443.     Point            polyPoint;
  1444. } TriangleInfo, *TriangleInfoPtr;
  1445.  
  1446. static pascal void            DrawThisTriangle(
  1447.         short                    depth,
  1448.         short                    deviceFlags,
  1449.         GDHandle                targetDevice,
  1450.         TriangleInfoPtr            triangleInfoPtr
  1451.     );
  1452.  
  1453. static void
  1454. DrawTriangle(
  1455.         PolyHandle                polyHandle,
  1456.         Point                    polyPoint,
  1457.         Boolean                    isSelected
  1458.     )
  1459. {
  1460.         TriangleInfo            triangleInfo;
  1461.         RgnHandle                drawingRgn;
  1462.         long                    savedA5;
  1463.         static DeviceLoopDrawingUPP drawingProcUPP;
  1464.         /*
  1465.          * Refresh A5 so we can use the QuickDraw globals, even if we're called
  1466.          * in a non-application context.
  1467.          */
  1468.         savedA5 = SetCurrentA5();
  1469.         triangleInfo.polyHandle = polyHandle;
  1470.         triangleInfo.polyPoint = polyPoint;
  1471.         OffsetPoly(polyHandle, polyPoint.h, polyPoint.v);
  1472.         if (isSelected)
  1473.             FillPoly(polyHandle, &qd.black);
  1474.         else {
  1475.             drawingRgn = NewRgn();
  1476.             OpenRgn();
  1477.             FramePoly(polyHandle);
  1478.             CloseRgn(drawingRgn);
  1479.             if (drawingProcUPP == NULL)
  1480.                 drawingProcUPP = NewDeviceLoopDrawingProc(DrawThisTriangle);
  1481.             DeviceLoop(drawingRgn, drawingProcUPP, (long) &triangleInfo, 0);
  1482.             DisposeRgn(drawingRgn);
  1483.         }
  1484.         /*
  1485.          * A thicker pen might look better for large font sizes, but it needs
  1486.          * to be done in a way that looks good for both button orientations.
  1487.          */
  1488.         FramePoly(polyHandle);
  1489.         OffsetPoly(polyHandle, -polyPoint.h, -polyPoint.v);
  1490.         SetA5(savedA5);
  1491. }
  1492.  
  1493. static pascal void
  1494. DrawThisTriangle(
  1495.         short                    depth,
  1496.         short                    deviceFlags,
  1497.         GDHandle                targetDevice,
  1498.         TriangleInfoPtr            triangleInfoPtr
  1499.     )
  1500. {
  1501.         RGBColor                foreColor;
  1502.         RGBColor                saveForeColor;
  1503.         RGBColor                backColor;
  1504.         short                    i;
  1505.         Rect                    polyRect;
  1506. #define TRI    (*triangleInfoPtr)
  1507.  
  1508.         UNUSED(deviceFlags);
  1509.         UNUSED(targetDevice);
  1510.         polyRect = (**TRI.polyHandle).polyBBox;
  1511.         LocalToGlobal(& ((Point *) &polyRect)[0]);
  1512.         LocalToGlobal(& ((Point *) &polyRect)[1]);
  1513.         if (depth > 1) {
  1514.             /*
  1515.              * We are drawing on a color device (or devices). Fill the unselected
  1516.              * triangle with a very light gray. The Finder extends this by filling
  1517.              * using the icon color instead of black.
  1518.              */
  1519.             GetForeColor(&foreColor);
  1520.             saveForeColor = foreColor;
  1521.             GetBackColor(&backColor);
  1522.             /*
  1523.              * This loop sets foreColor to a very light gray.
  1524.              */
  1525.             for (i = 0; i < 8; i++) {
  1526.                 if (GetGray(GetGDevice(), &backColor, &foreColor) == FALSE)
  1527.                     break;
  1528.             }
  1529.             RGBForeColor(&foreColor);
  1530.             FillPoly(TRI.polyHandle, &qd.black);
  1531.             RGBForeColor(&saveForeColor);
  1532.         }
  1533.         else {
  1534. #if MONOCHROME_FILL
  1535.             /*
  1536.              * We really don't need to do this, but it was useful in debugging
  1537.              * the algorithm on a machine with multiple displays. This fills
  1538.              * the polygon with a light gray texture on monochrome displays.
  1539.              * This is different from the Finder algorithm.
  1540.              */
  1541.             FillPoly(TRI.polyHandle, (ConstPatternParam) &qd.ltGray);
  1542. #else
  1543.             /*
  1544.              * Normally, we need only erase the interior of the polygon.
  1545.              */
  1546.             ErasePoly(TRI.polyHandle);
  1547. #endif
  1548. #undef TRI
  1549.         }
  1550. }
  1551.  
  1552. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  1553.  * SortTwistDownList
  1554.  *
  1555.  * Sort a list according to the comparison function. Algorithm adapted from
  1556.  * Numerical Recipes in C (Press, Flannery, Teukolsky, & Vetterling),
  1557.  * ISBN 0-521-35465-X. Algorithm 8.2 "Heapsort"
  1558.  *
  1559.  * This is *not* a recursive procedure. It allocates a temporary array that holds
  1560.  * a oopy of the list. The algorithm, then, is:
  1561.  *    1. Count the number of elements in the list - don't count sublists, though.
  1562.  *    2. Allocate a vector to hold the list handles and copy the list handles
  1563.  *        into the vector.
  1564.  *    3. Heapsort.
  1565.  *    4. The "Promotion" phase creates a new list set.
  1566.  * The function returns noErr on success, or a memory allocation error. By using
  1567.  * a non-recursive function, we avoid a problem where an ordered input list chews
  1568.  * up the application stack, which could lead to bizarre crashes.
  1569.  */
  1570. pascal OSErr
  1571. SortTwistDownList(
  1572.         TwistDownHdl            *twistDownHdlPtr,
  1573.         TwistDownSortCompareProc sortCompareProc,
  1574.         void                    *refCon
  1575.     )
  1576. {
  1577.         OSErr                    status;
  1578.         unsigned long            nElements;
  1579.         unsigned long            i;
  1580.         unsigned long            j;
  1581.         unsigned long            siftIndex;
  1582.         unsigned long            lastIndex;
  1583.         TwistDownHdl            twistDownHdl;
  1584.         TwistDownHdl            *heapVector;    /* One-origin vector        */
  1585.  
  1586.         nElements = 0;
  1587.         status = noErr;
  1588.         for (twistDownHdl = *twistDownHdlPtr;    /* Count list elements        */
  1589.                 twistDownHdl != NULL;
  1590.                 twistDownHdl = (**twistDownHdl).nextElement) {
  1591.             ++nElements;
  1592.         }
  1593.         if (nElements > 1) {            /* 0 or 1 elements aren't sorted    */
  1594.             heapVector = (TwistDownHdl *)
  1595.                         NewPtr((nElements + 1) * sizeof (TwistDownHdl));
  1596.             if (heapVector == NULL)
  1597.                 status = MemError();
  1598.             else {
  1599.                 /*
  1600.                  * Copy the list into the vector. Note that the vector is one-
  1601.                  * origin (this prevents an infinite loop if the index is zero).
  1602.                  */
  1603.                 for (i = 1, twistDownHdl = *twistDownHdlPtr;
  1604.                         twistDownHdl != NULL;
  1605.                         twistDownHdl = (**twistDownHdl).nextElement) {
  1606.                     heapVector[i++] = twistDownHdl;
  1607.                 }
  1608.                 siftIndex = (nElements >> 1) + 1;
  1609.                 lastIndex = nElements;
  1610.                 for (;;) {
  1611.                     if (siftIndex > 1)        /* Still in hiring phase?        */
  1612.                         twistDownHdl = heapVector[--siftIndex];
  1613.                     else {                    /* Else, in promotion phase?    */
  1614.                         /*
  1615.                          * Clear a space at the end of the vector, retire the
  1616.                          * top of the heap into it, if we're done with the
  1617.                          * last promotion, store the "least competent"
  1618.                          * worker, and exit.
  1619.                          */
  1620.                         twistDownHdl = heapVector[lastIndex];
  1621.                         heapVector[lastIndex] = heapVector[1];
  1622.                         if (--lastIndex <= 1) {
  1623.                             heapVector[1] = twistDownHdl;
  1624.                             break;            /* Exit the for (;;) loop        */
  1625.                         }
  1626.                     }
  1627.                     /*
  1628.                      * Whether we are in the hiring or promotion phase, we
  1629.                      * continue here to sift the current element into its
  1630.                      * proper position.
  1631.                      */
  1632.                     i = siftIndex;
  1633.                     j = siftIndex << 1;
  1634.                     while (j <= lastIndex) {
  1635.                         if (j < lastIndex
  1636.                          && (*sortCompareProc)(
  1637.                                     refCon,
  1638.                                     heapVector[j],
  1639.                                     heapVector[j + 1]
  1640.                                 ) < 0) {
  1641.                             ++j;
  1642.                         }
  1643.                         if ((*sortCompareProc)(
  1644.                                     refCon, twistDownHdl, heapVector[j]
  1645.                                 ) < 0) {
  1646.                             heapVector[i] = heapVector[j];
  1647.                             i = j;
  1648.                             j += i;
  1649.                         }
  1650.                         else {
  1651.                             break;
  1652.                         }
  1653.                     }
  1654.                     heapVector[i] = twistDownHdl;
  1655.                 }
  1656.                 /*
  1657.                  * heapVector has the sorted list. Build the result list.
  1658.                  */
  1659.                 *twistDownHdlPtr = heapVector[1];
  1660.                 for (i = 2; i <= nElements; i++)
  1661.                     (**(heapVector[i - 1])).nextElement = heapVector[i];
  1662.                 (**(heapVector[nElements])).nextElement = NULL;
  1663.                 DisposePtr((Ptr) heapVector);
  1664.             }
  1665.         }
  1666.         return (status);
  1667.  
  1668. }
  1669.  
  1670.  
  1671. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  1672.  * AdjustHorizontalScrollbar
  1673.  *
  1674.  * This is called when the user clicks in the horizontal scrollbar that was
  1675.  * created when the list was created. Note that we handle the scrollbar here,
  1676.  * as it is not stored within the ListManager list record.
  1677.  */
  1678. static void
  1679. AdjustHorizontalScrollbar(
  1680.         ListHandle                theList
  1681.     )
  1682. {
  1683.         short                    horizontalMax;
  1684.         ControlHandle            theControl;
  1685.         
  1686.         theControl = PRIVATE.hScroll;
  1687.         horizontalMax = kMaxHorizontalScroll - width((**theControl).contrlRect);
  1688.         if (horizontalMax < 0)
  1689.             horizontalMax = 0;
  1690.         SetControlMinimum(theControl, 0);
  1691.         SetControlMaximum(theControl, horizontalMax);
  1692.         HiliteControl(
  1693.             theControl,
  1694.             (horizontalMax == 0) ? kDisabledControl : kActiveControl
  1695.         );
  1696. }
  1697.  
  1698. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  1699.  * ScrollTwistDownActionProc
  1700.  *
  1701.  * This is called by TrackControl when the user clicks in the horizontal
  1702.  * scrollbar that was created when the list was created. Note that we handle
  1703.  * the scrollbar here, as it is not stored within the ListManager list record.
  1704.  */
  1705. static pascal void
  1706. ScrollTwistDownActionProc(
  1707.         register ControlHandle    theControl,
  1708.         short                    partCode
  1709.     )
  1710. {
  1711.         short                    delta;
  1712.         
  1713.         delta = (width((**theControl).contrlRect) * 7) / 8;
  1714.         switch (partCode) {
  1715.         case kInUpButtonControlPart:    delta = -CharWidth('M');        break;
  1716.         case kInPageUpControlPart:        delta = (-delta);                break;
  1717.         case kInDownButtonControlPart:    delta = CharWidth('M');            break;
  1718.         case kInPageDownControlPart:    /* All set */                    break;
  1719.         default:                        return;            /* Mouse exited control    */
  1720.         }
  1721.         SetControlValue(theControl, GetControlValue(theControl) + delta);
  1722.         ScrollTwistDownList(theControl);
  1723. }
  1724.  
  1725. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  1726.  * ScrollTwistDownList
  1727.  *
  1728.  * This is called when the user clicks in the horizontal scrollbar that was
  1729.  * created when the list was created. This is the function that modifies the
  1730.  * scrollbar.
  1731.  */
  1732. static void
  1733. ScrollTwistDownList(
  1734.         register ControlHandle    theControl
  1735.     )
  1736. {
  1737.         ListHandle                theList;
  1738.         short                    delta;
  1739.         RgnHandle                clipRgn;
  1740.         RgnHandle                updateRgn;
  1741.         Rect                    viewRect;
  1742.         
  1743.         theList = (ListHandle) GetControlReference(theControl);
  1744.         /*
  1745.          * LIST.indent.h is negative when the cell is scrolled left. Get its
  1746.          * current amount (as a positive value) and set delta to the amount
  1747.          * that must be scrolled. Delta will be positive to scroll right
  1748.          * (meaning that the scroll bar moved left). This probably won't
  1749.          * work for a right-to-left language.
  1750.          */
  1751.         delta = kZeroIndent - LIST.indent.h - GetControlValue(theControl);
  1752.         if (delta != 0) {
  1753.             /*
  1754.              * We must scroll the list. Get a clip rectangle so the scrolling
  1755.              * is limited to the drawing area, scroll it, and update anything
  1756.              * that came into view. Hmm, should the buttons ever scroll?
  1757.              */
  1758.             viewRect = LIST.rView;
  1759.             clipRgn = NewRgn();
  1760.             updateRgn = NewRgn();
  1761.             GetClip(clipRgn);
  1762.             ClipRect(&viewRect);
  1763.             ScrollRect(&viewRect, delta, 0, updateRgn);
  1764.             LIST.indent.h += delta;
  1765.             LUpdate(updateRgn, theList);
  1766.             SetClip(clipRgn);
  1767.             DisposeRgn(updateRgn);
  1768.             DisposeRgn(clipRgn);
  1769.         }
  1770. }
  1771.  
  1772. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  1773.  * Print Manager
  1774.  *
  1775.  * The following section lets the user print a twistdown list.
  1776.  */
  1777.  
  1778. static pascal OSErr            DefaultTwistDownPrintSetupProc(
  1779.         ListHandle                theList,
  1780.         THPrint                    hPrint,
  1781.         void                    *clientData,
  1782.         StringPtr                dateString
  1783.     );
  1784. static pascal OSErr            DefaultTwistDownPrintImageProc(
  1785.         ListHandle                theList,
  1786.         THPrint                    hPrint,
  1787.         void                    *clientData,
  1788.         StringPtr                dateString,
  1789.         const Rect                *pageRect,
  1790.         short                    pageNumber
  1791.     );
  1792.  
  1793. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  1794.  * PrintTwistDownList
  1795.  *
  1796.  * Mainline print handler. This calls task-specific routines to count the number
  1797.  * of pages and to image each page. The caller may provide special routines for
  1798.  * these tasks, or use the default calls (that presume text-only list elements).
  1799.  * This is modified from Rich Siegel's generic print driver.
  1800.  */
  1801. pascal OSErr
  1802. PrintTwistDownList(
  1803.         ListHandle                theList,
  1804.         THPrint                    *printHandlePtr,
  1805.         Boolean                    doStyleDialog,
  1806.         TwistDownPrintSetupProc    twistDownPrintSetupProc,
  1807.         TwistDownPrintImageProc    twistDownPrintImageProc,
  1808.         TwistDownPrintExitProc    twistDownPrintExitProc,
  1809.         void                    *clientData
  1810.     )
  1811. {
  1812.         OSErr                    status;            /* Current error                */
  1813.         THPrint                    hPrint;            /* Copy of *printHandlePtr        */
  1814.         Boolean                    printIsOpen;    /* PROpen ... PRClose            */
  1815.         Boolean                    docIsOpen;        /* PROpenDoc ... PRCloseDoc        */
  1816.         short                    nCopies;        /* Number of copies to print    */
  1817.         short                    iCopy;            /* Which copy are we printing?    */
  1818.         short                    pageNumber;        /* Which page are we printing?    */
  1819.         Rect                    pageRect;        /* Current page image rectangle    */
  1820.         short                    printDevice;    /* What kind of printer?        */
  1821.         Boolean                    draftMode;        /* Draft or spool?                */
  1822.         TPPrPort                printPort;        /* The print port                */
  1823.         TPrStatus                printStatus;    /* PrPicFile status info        */
  1824.         GrafPtr                    savePort;        /* Old GrafPort                    */
  1825.         unsigned long            now;            /* When we're called            */
  1826.         Str255                    dateString;        /* Handy date + time string        */
  1827. #ifndef bDevCItoh
  1828. #define bDevCItoh            1
  1829. #endif
  1830. #ifndef bDevLaser
  1831. #define bDevLaser            3
  1832. #endif
  1833.         enum {
  1834.             kImageWriter        = bDevCItoh,
  1835.             kLaserWriter        = bDevLaser
  1836.         };
  1837. /*
  1838.  * This macro exits the print handler on any error.
  1839.  */
  1840. #define CheckError(stat) do {                    \
  1841.         if ((status = (stat)) != noErr)            \
  1842.             goto exit;                            \
  1843.     } while (0)
  1844. #if 1 /* Debug only */
  1845. #define CHECK(stat, why) do {                                    \
  1846.         OSErr                checkStat;                            \
  1847.         extern short NonFatalError(OSErr, ConstStr255Param);    \
  1848.         if ((checkStat = (stat)) != noErr)                        \
  1849.             NonFatalError(checkStat, why);                        \
  1850.     } while (0)
  1851. #else
  1852. #define CHECK(stat, why) /* Nothing */
  1853. #endif
  1854.  
  1855.         GetPort(&savePort);
  1856.         status = noErr;
  1857.         printIsOpen = FALSE;
  1858.         docIsOpen = FALSE;
  1859.         if (twistDownPrintSetupProc == NULL)
  1860.             twistDownPrintSetupProc = DefaultTwistDownPrintSetupProc;
  1861.         if (twistDownPrintImageProc == NULL)
  1862.             twistDownPrintImageProc = DefaultTwistDownPrintImageProc;
  1863.         /*
  1864.          * Set the date and time
  1865.          */
  1866.         GetDateTime(&now);
  1867.         IUDateString(now, abbrevDate, dateString);
  1868.         dateString[++dateString[0]] = ' ';
  1869.         dateString[++dateString[0]] = '/';
  1870.         iCopy = ++dateString[0];
  1871.         IUTimeString(now, FALSE, &dateString[iCopy]);
  1872.         dateString[0] += (dateString[iCopy] + 1);
  1873.         dateString[iCopy] = ' ';
  1874.         /*
  1875.          * If there is no Print Handle, allocate one and fail on errors. On exit,
  1876.          * the print handler is retained so that multiple printouts retain the
  1877.          * same user selections.
  1878.          */
  1879.         PrOpen();
  1880.         status = PrError();
  1881.         CHECK(status, "\pCan't open print manager");
  1882.         CheckError(status);
  1883.         printIsOpen = TRUE;
  1884.         if (*printHandlePtr == NULL) {
  1885.             *printHandlePtr = (THPrint) NewHandle(sizeof (TPrint));
  1886.             if (*printHandlePtr == NULL) {
  1887.                 status = MemError();
  1888.                 CHECK(status, "\pNewHandle(sizeof (TPrint)) failed");
  1889.                 goto exit;
  1890.             }
  1891.             PrintDefault(*printHandlePtr);
  1892.         }
  1893.         hPrint = (*printHandlePtr);
  1894.         /*
  1895.          * Validate the Print Handle and call the Print Style dialog if necessary.
  1896.          * If the user cancels, exit (noErr). Then call the job dialog to get the
  1897.          * number of copies (exit on cancel here, too). Note that we don't exit
  1898.          * with userCanceledErr, as this is merely informative.
  1899.          */
  1900.         if (PrValidate(hPrint) || doStyleDialog) {
  1901.             if (PrStlDialog(hPrint) == FALSE) {
  1902.                 CHECK(userCanceledErr, "\pUser canceled style dialog");
  1903.                 goto exit;
  1904.             }
  1905.         }
  1906.         if (PrJobDialog(hPrint) == FALSE) {
  1907.             CHECK(userCanceledErr, "\pUser canceled job dialog");
  1908.             goto exit;
  1909.         }
  1910.         /*
  1911.          * Setup is done, call the user's setup procedure and exit on errors.
  1912.          * The setup function must set the number of pages in the document and
  1913.          * return noErr to continue.
  1914.          */
  1915.         SetCursor(*GetCursor(watchCursor));
  1916.         status = (*twistDownPrintSetupProc)
  1917.                     (theList, hPrint, clientData, dateString);
  1918.         CHECK(status, "\pUser setup function failed");
  1919.         if (status != noErr)
  1920.             goto exit;
  1921.         /*
  1922.          * Grab some information for the loops that follow.
  1923.          * printDevice is the printing device. This is only interesting for
  1924.          * ImageWriter (StyleWriter?) compatibility, and hasn't been tested
  1925.          * in many years.
  1926.          */
  1927.         printDevice = ((**hPrint).prStl.wDev >> 8) & 0xFF;
  1928.         draftMode = (**hPrint).prJob.bJDocLoop == bDraftLoop;
  1929.         if (draftMode && printDevice == kImageWriter)
  1930.             nCopies = (**hPrint).prJob.iCopies;
  1931.         else {
  1932.             nCopies = 1;
  1933.         }
  1934.         /*
  1935.          * Printing begins here.
  1936.          */
  1937.         printPort = PrOpenDoc(hPrint, NULL, NULL);
  1938.         docIsOpen = TRUE;
  1939.         status = PrError();
  1940.         CHECK(status, "\pCan't open document to print");
  1941.         CheckError(status);
  1942.         /*
  1943.          * Note: if you select "Print to EPS file", you can only image
  1944.          * one page, the first in this case.
  1945.          */
  1946.         for (iCopy = 1; iCopy <= nCopies; iCopy++) {
  1947.             for (pageNumber = (**hPrint).prJob.iFstPage;
  1948.                     pageNumber <= (**hPrint).prJob.iLstPage;
  1949.                     pageNumber++) {
  1950.                 SetCursor(*GetCursor(watchCursor));
  1951.                 PrOpenPage(printPort, NULL);
  1952.                 status = PrError();
  1953.                 if (status == noErr) {
  1954.                     pageRect = (**hPrint).prInfo.rPage;
  1955.                     status = (*twistDownPrintImageProc)(
  1956.                                 theList,
  1957.                                 hPrint,
  1958.                                 clientData,
  1959.                                 dateString,
  1960.                                 &pageRect,
  1961.                                 pageNumber
  1962.                             );
  1963.                 }
  1964.                 PrClosePage(printPort);
  1965.                 if (status == noErr)
  1966.                     status = PrError();
  1967.                 CHECK(status, "\pPrint page error");
  1968.                 CheckError(status);
  1969.             }
  1970.         }
  1971.         /*
  1972.          * Normal exit
  1973.          */
  1974.         SetPort(savePort);
  1975.         PrCloseDoc(printPort);
  1976.         docIsOpen = FALSE;
  1977.         status = PrError();
  1978.         CHECK(status, "\pCan't close document after printing complete");
  1979.         CheckError(status);
  1980.         if (draftMode == FALSE) {
  1981.             PrPicFile(hPrint, NULL, NULL, NULL, &printStatus);
  1982.             status = PrError();
  1983.             CHECK(status, "\pCan't spool document after printing complete");
  1984.         }
  1985.         /*
  1986.          * Everyone exits here.
  1987.          */
  1988. exit:    SetPort(savePort);
  1989.         if (docIsOpen)
  1990.             PrCloseDoc(printPort);
  1991.         if (printIsOpen)
  1992.             PrClose();
  1993.         if (twistDownPrintExitProc != NULL)
  1994.             status = (*twistDownPrintExitProc)(theList, clientData, status);
  1995.         InitCursor();
  1996.         return (status);
  1997. }
  1998.  
  1999. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  2000.  * DefaultTwistDownPrintSetupProc
  2001.  *
  2002.  * This will be called if the user didn't provide a print setup procedure. Our
  2003.  * default output page will have the date on the top-left and the page number on
  2004.  * the top right (both in 9 pt Helvetica Bold). This function must set the actual
  2005.  * number of pages that are to be printed.
  2006.  *
  2007.  * Note: the print functions have not been upgraded for right-to-left languages.
  2008.  */
  2009. static pascal OSErr
  2010. DefaultTwistDownPrintSetupProc(
  2011.         ListHandle                theList,
  2012.         THPrint                    hPrint,
  2013.         void                    *clientData,
  2014.         StringPtr                dateString
  2015.     )
  2016. {
  2017.         unsigned short            lineHeight;
  2018.         unsigned short            linesInPrintout;
  2019.         unsigned short            linesPerPage;
  2020.         unsigned short            nPages;
  2021.         unsigned short            headerHeight;
  2022.         Rect                    printRect;
  2023.         FontInfo                info;
  2024.         short                    fontNumber;
  2025.         
  2026.         UNUSED(clientData);
  2027.         UNUSED(dateString);
  2028.         SetPort(LIST.port);
  2029.         GetFNum(kPrintoutHeaderFont, &fontNumber);
  2030.         TextFont(fontNumber);
  2031.         TextSize(kPrintoutHeaderFontSize);
  2032.         GetFontInfo(&info);
  2033.         headerHeight = info.ascent + info.descent + info.leading;
  2034.         /*
  2035.          * Get the display page rectangle, remove the header, and determine
  2036.          * the number of lines that will fit on one page and, from that,
  2037.          * the number of pages in the printout. If this is less than the
  2038.          * default (999), adjust the print record.
  2039.          */
  2040.         printRect = (**hPrint).prInfo.rPage;
  2041.         printRect.top += (headerHeight + kPrintoutHeaderGap);
  2042.         linesInPrintout = height(LIST.dataBounds);
  2043.         TextFont(PRIVATE.fontNumber);
  2044.         TextSize(PRIVATE.fontSize);
  2045.         TextFace(normal);
  2046.         GetFontInfo(&info);
  2047.         lineHeight = info.ascent + info.descent + info.leading;
  2048.         linesPerPage = height(printRect) / lineHeight;
  2049.         nPages = (linesInPrintout + linesPerPage - 1) / linesPerPage;
  2050.         /*
  2051.          * We want to print nPages. Set this into the print record if the
  2052.          * caller asked for more (i.e. for the default "all pages").
  2053.          */
  2054.         if ((**hPrint).prJob.iLstPage > nPages)
  2055.             (**hPrint).prJob.iLstPage = nPages;
  2056.         return (noErr);
  2057. }
  2058.  
  2059. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  2060.  * DefaultTwistDownPrintImageProc
  2061.  *
  2062.  * Image the current page. This must duplicate some of the code from the
  2063.  * print setup function (above). This needs work for right-to-left languages.
  2064.  *
  2065.  * The page is imaged using the currently selected display font and font size.
  2066.  */
  2067. static pascal OSErr
  2068. DefaultTwistDownPrintImageProc(
  2069.         ListHandle                theList,
  2070.         THPrint                    hPrint,
  2071.         void                    *clientData,
  2072.         StringPtr                dateString,
  2073.         const Rect                *pageRect,
  2074.         short                    pageNumber
  2075.     )
  2076. {
  2077.         unsigned short            i;
  2078.         unsigned short            lastRow;
  2079.         unsigned short            linesPerPage;
  2080.         unsigned short            headerHeight;
  2081.         Rect                    printRect;
  2082.         short                    lineHeight;
  2083.         Cell                    listCell;
  2084.         FontInfo                info;
  2085.         short                    fontNumber;
  2086.         Str255                    work;
  2087.         short                    cellSize;
  2088.         TwistDownHdl            twistDownHandle;
  2089.         register TwistDownPtr    twistDownPtr;
  2090.         signed char                elementLockState;
  2091.         
  2092.         UNUSED(clientData);
  2093.         UNUSED(hPrint);
  2094.         /*
  2095.          * First, image the header.
  2096.          */
  2097.         GetFNum(kPrintoutHeaderFont, &fontNumber);
  2098.         TextFont(fontNumber);
  2099.         TextSize(kPrintoutHeaderFontSize);
  2100.         TextFace(kPrintoutHeaderStyle);
  2101.         GetFontInfo(&info);
  2102.         headerHeight = info.ascent + info.descent + info.leading;
  2103.         MoveTo(pageRect->left, pageRect->top + info.ascent);
  2104.         DrawString(dateString);
  2105.         pstrcpy(work, "\pPage: ");
  2106.         i = work[0];
  2107.         NumToString(pageNumber, &work[i]);
  2108.         work[0] += work[i];
  2109.         work[i] = ' ';
  2110.         MoveTo(pageRect->right - StringWidth(work), pageRect->top + info.ascent);
  2111.         DrawString(work);
  2112.         printRect = *pageRect;
  2113.         printRect.top += (headerHeight + kPrintoutHeaderGap);
  2114.         /*
  2115.          * Now, do the list data itself.
  2116.          */
  2117.         TextFont(PRIVATE.fontNumber);
  2118.         TextSize(PRIVATE.fontSize);
  2119.         TextFace(normal);
  2120.         GetFontInfo(&info);
  2121.         lineHeight = info.ascent + info.descent + info.leading;
  2122.         linesPerPage = height(printRect) / lineHeight;
  2123.         listCell.h = 0;
  2124.         listCell.v = (pageNumber - 1) * linesPerPage + LIST.dataBounds.top;
  2125.         lastRow = listCell.v + linesPerPage;
  2126.         if (lastRow > LIST.dataBounds.bottom)
  2127.             lastRow = LIST.dataBounds.bottom;
  2128.         printRect.bottom = printRect.top + lineHeight;
  2129.         while (listCell.v < lastRow) {
  2130.             cellSize = sizeof twistDownHandle;
  2131.             LGetCell(&twistDownHandle,    &cellSize, listCell, theList);
  2132.             if (cellSize == sizeof twistDownHandle && twistDownHandle != NULL) {
  2133.                 elementLockState = HGetState((Handle) twistDownHandle);
  2134.                 HLock((Handle) twistDownHandle);
  2135.                 twistDownPtr = (*twistDownHandle);
  2136.                 (PRIVATE.drawProc)(theList, twistDownPtr, &printRect);
  2137.                 HSetState((Handle) twistDownHandle, elementLockState);
  2138.             }
  2139.             OffsetRect(&printRect, 0, lineHeight);
  2140.             ++listCell.v;
  2141.         }
  2142.         return (noErr);
  2143. }
  2144.  
  2145. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  2146.  * DefaultTwistDownDrawProc
  2147.  *
  2148.  * Draw the current list element in the viewRect. The caller has setup the font
  2149.  * information.
  2150.  */
  2151. static pascal void
  2152. DefaultTwistDownDrawProc(
  2153.         ListHandle                theList,            /* The list itself            */
  2154.         TwistDownPtr            twistDownPtr,        /* Locked data handle        */
  2155.         const Rect                *viewRect            /* Draw in this area        */
  2156.     )
  2157. {
  2158.         short                    textWidth;
  2159. #define ELEM (*twistDownPtr)
  2160.  
  2161.         if (PRIVATE.isLeftJustify)
  2162.             MoveTo(viewRect->left, viewRect->top + LIST.indent.v);
  2163.         else {
  2164.             textWidth = TextWidth(ELEM.data, 0, ELEM.dataLength);
  2165.             MoveTo(viewRect->right - textWidth, viewRect->top + LIST.indent.v);
  2166.         }
  2167.         DrawText(ELEM.data, 0, ELEM.dataLength);
  2168. #undef ELEM
  2169. }
  2170.  
  2171. static void
  2172. pstrcpy(
  2173.         StringPtr                dst,
  2174.         ConstStr255Param        src
  2175.     )
  2176. {
  2177.         BlockMoveData(src, dst, src[0] + 1);
  2178. }
  2179.  
  2180. static void
  2181. pstrcat(
  2182.         StringPtr                dst,
  2183.         ConstStr255Param        src
  2184.     )
  2185. {
  2186.         short                    length;
  2187.         
  2188.         length = 255 - dst[0];
  2189.         if (length > src[0])
  2190.             length = src[0];
  2191.         BlockMoveData(&src[1], &dst[1] + dst[0], length);
  2192.         dst[0] += length;
  2193. }
  2194.